﻿
// canvasはDOMElementだけに依存する

var SvgCanvas = Class.create();
SvgCanvas.prototype = {
	_canvas: null,

	init: function (id) {
		this._canvas = document.getElementById(id);

		return (this._canvas);
	},

	element: function () {
		return this._canvas;
	},

	clear: function () {
		var ctx = this._canvas.getContext('2d');
		var dims = this.dimensions();
		ctx.clearRect(0, 0, dims[0], dims[1]);
	},

	render: function (queue) {
		this.clear();

		// レンダリング
		for (var i = 0, l = queue.length; i < l; i++) {
			if (queue[i].Type == "pen") {
				this.stroke(
                    queue[i].Points,
                    queue[i].Color,
                    queue[i].Weight
                  );
			}
		}

	},

	stroke: function (pos, color, weight) {
		if (pos.length <= 0)
			return;

		var ctx = this._canvas.getContext('2d');
		ctx.lineCap = 'round';
		ctx.strokeStyle = color;
		ctx.lineWidth = weight;

		ctx.beginPath();
		ctx.globalAlpha = 1.0;
		if (pos.length == 2) {
			// 前回の位置から今回の位置まで
			ctx.moveTo(pos[0].X, pos[0].Y);
			ctx.lineTo(pos[1].X, pos[1].Y);
		}
		else {
			// 最初の位置から全ての位置
			ctx.moveTo(pos[0].X, pos[0].Y);
			for (var i = 1; i < pos.length; i++) {
				ctx.lineTo(pos[i].X, pos[i].Y);
			}
		}

		ctx.stroke();
	},

	dimensions: function () {
		var ctx = this._canvas.getContext('2d');
		return ([$(this._canvas).width(), $(this._canvas).height()]);
	}
}

// queueuは誰にも依存しない

var StrokeQueue = Class.create();
StrokeQueue.prototype = {
	_queue: [],
	init: function () {
	},

	clear: function () {
		this._queue = [];
	},

	reset: function (strokes) {
		this._queue = strokes;
	},

	enqueue: function (stroke) {
		// タイミングはどこでもいいけど、queueに残ってる送信済みの
		// strokeは消してしまいたいね。
		// 残してるとcanvasに無駄なstrokeを描いちゃう。queueを分けるべき？
		this._queue.push(stroke);
	},

	unsend: function () {
		var data = [];
		var queue = this._queue;
		for (var i = 0, l = queue.length; i < l; i++) {
			if (queue[i].Id === undefined) {
				data.push(queue[i]);
			}
		}

		return data;
	},

	strokes: function () {
		return this._queue;
	}
}

// queueとcanvasに依存する → graffiti

var NetworkCommunicator = Class.create();
NetworkCommunicator.prototype = {
	_graffiti: null,
	_maxStrokeId: 0,
	_asyncHandle: null,
	_changer: false,
	_rootPath: '',

	asyncFuncs: {
		loading: function () {
			$("#loading").show();
		},
		loaded: function () {
			$("#loading").hide();
		},
		error: function (xhr, status) {
			alert("[" + status + "]" + xhr.responseText);
		}
	},

	init: function (rootPath, graffiti, sessionElement) {
		var self = this;
		this._rootPath = rootPath;
		this.graffitiId = function () { return graffiti.graffitiId(); };
		this.sessionId = function () { return graffiti.sessionId(); };
		this.queue = function () { return graffiti.queue(); };
		this.render = function () { graffiti.render(); };

		// 初回
		this.load();

		// 1秒ごとに
		_asyncHandle = setInterval(function () {
			var unsend = self.queue().unsend();
			debug("unsend:" + unsend.length);

			if (self._changer) {
				self.save();
			} else {
				self.load();
				setTimeout(function(){ self.sessions(sessionElement, self.graffitiId())}, 100);
			}
			self._changer = !self._changer;
		}, 500);
	},

	sessions: function (element, id) {
		if ($.active > 0)
			return;

		var self = this;
		$.ajax({
			type: "get",
			url: self._rootPath + "graffitis.svc/GetSessions?id=" + id,
			cache: false,
			contentType: "application/json; charset=utf-8",
			dataType: "json",
			success: function (d) {
				$(element).html("");

				if (d.d.length == 0)
					return;

				$(d.d).each(function (idx, val) {
					$(element).append($("<li />").html(val));
				});
			},
			error: function (xhr, status) {
				alert("[" + status + "]" + xhr.responseText);
			},
			complete: self.asyncFuncs.loaded
		});
	},

	load: function (startId) {
		if (startId === undefined)
			startId = this._maxStrokeId;

		var self = this;
		var graffitiId = this.graffitiId();
		var sessionId = this.sessionId();

		if ($.active > 0)
			return;

		self.asyncFuncs.loading();
		$.ajax({
			type: "get",
			url: self._rootPath + "graffitis.svc/Strokes",
			data: "$filter=GraffitiId eq " + graffitiId + " and Id gt " + startId + "&$expand=Points",
			cache: false,
			contentType: "application/json; charset=utf-8",
			dataType: "json",
			success: function (d) {
				if (d.d.length == 0)
					return;

				$(d.d).each(function (idx, val) {
					self.queue().enqueue(val);
					// 最大値
					self._maxStrokeId = val.Id;
				});

				self.render();
			},
			error: function (xhr, status) {
				alert("[" + status + "]" + xhr.responseText);
			},
			complete: self.asyncFuncs.loaded
		});
	},

	save: function () {
		var self = this;
		var data = this.queue().unsend();

		if ($.active > 0 || data.length == 0)
			return;

		$(data).each(function (idx, val) {
			setTimeout(function () {
				$.ajax({
					type: "post",
					url: self._rootPath + "graffitis.svc/Strokes",
					contentType: "application/json; charset=utf-8",
					data: $.toJSON(val),
					dataType: "json",
					success: function (d) {
						data[idx].Id = -1;
					},
					error: function (xhr, status) {
						alert("[" + status + "]" + xhr.responseText);
					},
					complete: function () {
					}
				});
			}, 10);
		});
	}
}

// queueとcanvasに依存する → graffiti

var BaseInput = Class.create();
BaseInput.prototype = {
    _isDown: false,
    _doGesture: false,
    _canvasElement: null,
    _colorElement: null,

    init: function (graffiti) {
        this.zoom = function () { return graffiti.zoom(); };
        this.canvasPosition = function () { return graffiti.canvasPosition(); };
        this.graffitiId = function () { return graffiti.graffitiId(); };
        this.sessionId = function () { return graffiti.sessionId(); };
        this.render = function () { graffiti.render(); };

        this._canvasElement = graffiti.canvasElement();
        this._colorElement = graffiti.colorElement();
    },

    canvasSize: function () {
        var pos = this.canvasPosition();
        return {
            left: pos.left,
            top: pos.top,
            width: $(this._canvasElement).width(),
            height: $(this._canvasElement).height()
        };
    },

    point: function (touch) {
        var zoom = this.zoom();
        var parent = $(this._canvasElement).parent();

        var touchPos = { X: touch.pageX, Y: touch.pageY };
        var canvasPos = this.canvasSize();

        // canvas位置とのマッピング
        var p = {
            X: parseInt((touchPos.X / zoom - canvasPos.left)),
            Y: parseInt((touchPos.Y / zoom - canvasPos.top))
        };
        return p;
    },

    hit: function (touch) {
        //var element = document.elementFromPoint(e.pageX,e.pageY);
        var point = this.point(touch);
        var cpos = this.canvasSize();
        if (point.X < 0 || cpos.width < point.X)
            return false;

        if (point.Y < 0 || cpos.height < point.Y)
            return false;

        return true;
    },

    start: function (target, e) {
        var touch = e.touches ? e.touches[0] : e;
        if (!this.hit(touch))
            return;

        e.preventDefault();

        this._isDown = true;
        var color = "#000";
        if (this._colorElement)
            color = $(this._colorElement).val();

        target._stroke = {
            GraffitiId: this.graffitiId(),
            SessionId: this.sessionId(),
            Type: 'pen',
            Color: color,
            Weight: 5,
            Points: []
        };
        target.point(e);
    },

    end: function (target, e) {
        e.preventDefault();
        var self = this;

        // gesture
        if (this._doGesture) {
            target.point(e);
            return;
        }

        if (this._isDown) {
            this._isDown = false;
            var s = this.optimization(target._stroke.Points);
            target._stroke.Points = s;
            target._queue.enqueue(target._stroke);
            setTimeout(function () {
                self.render();
                //target._canvas.render(target._queue.strokes());
            }, 10);
        }

        target.point(e);
    },

    move: function (target, e) {
        e.preventDefault();

        var pos = target.point(e);
        if (this._isDown && pos != null) {
            target._stroke.Points.push(pos);
            setTimeout(function () {
                target._canvas.stroke(
                    target._stroke.Points,
                    target._stroke.Color,
                    target._stroke.Weight
                );
            }, 10);
        }
    },

    isDown: function () {
        return this._isDown;
    },

    doGesture: function (doing) {
        if (doing !== undefined)
            this._doGesture = doing;

        return this._doGesture;
    },

    clear: function () {
        this._isDown = false;
        this._doGesture = false;
    },

    radian: function (spos, epos) {
        var x = epos.X - spos.X;
        var y = epos.Y - spos.Y;

        if (x == 0)
            return (0);
        else
            return (Math.floor(Math.atan2(y, x) * 180 / Math.PI));
    },

    optimization: function (s) {
        if (s.length > 1) {
            // 同じ線上の点を消す。
            var re_strk = [], sp = 0, np = 1, tp = 2, i = 1;
            re_strk.push(s[0]);
            while (tp < s.length) {
                // 同じ角度？
                if (this.radian(s[sp], s[np]) != this.radian(s[sp], s[tp])) {
                    re_strk.push(s[np]);
                    sp = np;
                }
                np++;
                tp++;
            }
            re_strk.push(s[np]);
            return (re_strk);
        }
        else
            return (s);
    }
}

// base inputに依存する(なのでqueueとcanvasにも依存する)

var TouchPanelInput = Class.create();
TouchPanelInput.prototype = {
    _base: null,
    _stroke: [],
    _queue: null,
    _canvas: null,
    init: function (graffiti) {
        this.canvasPosition = function () { return graffiti.canvasPosition() };
        this.zoom = function () { return graffiti.zoom.apply(graffiti, arguments); };

        this._canvas = graffiti.canvas();
        this._queue = graffiti.queue();         // baseで使うよ
        this._base = new BaseInput(graffiti);
        var self = this;
        document.addEventListener("touchstart", function (e) {
            self._base.start(self, e);
        }, false);

        document.addEventListener("touchmove", function (e) {
            self._base.move(self, e);
        }, false);

        document.addEventListener("touchend", function (e) {
            self._base.doGesture(false);

            self._base.end(self, e);
        }, false);

        document.addEventListener("touchcancel", function (e) {
            e.preventDefault();
            self.clear();
            self.point(e);
        }, false);
    },

    clear: function () {
        this._base.clear();

        this._prevPos = null;
        this._prevZoom = 0;
    },

    isDown: function () {
        return this._base.isDown();
    },

    // eはMobile Safariのeventでよろしく
    // touch/gestureが不要な入力デバイスなら
    // jQueryのeventでいいけどさ。
    _prevPos: null,
    _prevZoom: 0,

    point: function (e) {
        var drawPos = this._base.point(e.touches[0]);
        var isGesture = e.touches.length > 1;
        // 移動させるよ
        if (isGesture) {
            this._base.doGesture(true);
            var pos = { X: e.touches[0].pageX, Y: e.touches[0].pageY };
            if (this._prevPos != null) {
                var move = {
                    X: (pos.X - this._prevPos.X),
                    Y: (pos.Y - this._prevPos.Y)
                };

                var cpos = this.canvasPosition();
                $(this._canvas.element()).css({
                    left: cpos.left + move.X + "px",
                    top: cpos.top + move.Y + "px"
                });
                this._prevPos = null;

                // zoom it!
                var realZoom = Math.max(Math.abs(e.touches[0].pageX - e.touches[1].pageX),
                                  Math.abs(e.touches[0].pageY - e.touches[1].pageY));

                if (this._prevZoom > 0) {
                    var zoom = this.zoom();

                    if (realZoom > this._prevZoom)
                        zoom += (realZoom - this._prevZoom) / 500;
                    else if (realZoom < this._prevZoom)
                        zoom -= (this._prevZoom - realZoom) / 500;

                    this.zoom(zoom);
                };
                this._prevZoom = realZoom;
            }

            this._prevPos = pos;
            drawPos = null;
        } else {
            this._prevZoom = 0;
            this._prevPos = null;
        }

        return drawPos;
    }
}

// base inputに依存する(なのでqueueとcanvasにも依存する)

var MouseInput = Class.create();
MouseInput.prototype = {
    _base: null,
    _stroke: [],
    _isDown: false,
    _queue: null,

    _canvas: null,

    init: function (graffiti) {
        this._canvas = graffiti.canvas();
        this._queue = graffiti.queue();         // baseで使うよ
        this._base = new BaseInput(graffiti);

        var self = this;
        document.addEventListener("mousedown", function (e) {
            self._base.start(self, e);
        }, false);

        document.addEventListener("mousemove", function (e) {
            self._base.move(self, e);
        }, false);

        document.addEventListener("mouseup", function (e) {
            self._base.end(self, e);
        }, false);

        $(document).mousewheel(function (e, delta) {
            var zoom = graffiti.zoom();
            if (delta > 0)
                zoom += 0.02;
            else if (delta < 0)
                zoom -= 0.02;

            graffiti.zoom(zoom);
        });
    },

    isDown: function () {
        return this._base.isDown();
    },

    // eはMobile Safariのeventでよろしく
    // touch/gestureが不要な入力デバイスなら
    // jQueryのeventでいいけどさ。
    point: function (e) {
        return this._base.point(e);
    }
}

// すべてのクラスに依存する

var Graffiti = Class.create();
Graffiti.prototype = {
    _zoom: 1.0,
    _size: { width: 600, height: 800 },
    _graffitiId: null,
    _sessionId: null,
    _canvas: null,
    _queue: null,
    _canvasId: '',
    _colorId: '',

    isDown: null,   // ずるシテごめんね

    init: function (canvasId, colorId, graffitiId, sessionId, options) {
        this._canvasId = canvasId;
        this._colorId = colorId;
        this._graffitiId = graffitiId || 0;
        this._sessionId = sessionId || 0;

        var zoom = navigator.platform == "iPhone" ? 0.43 :  // iPhone
                 navigator.platform == "iPad" ? 1.0     // iPad
                : 0.75;                                   // PC

        var canvas = new SvgCanvas(canvasId);
        var queue = new StrokeQueue();

        var opt = { zoom: zoom, canvas: canvas, queue: queue };
        for (var prop in options)
            opt[prop] = options[prop];

        this._canvas = opt.canvas;
        this._queue = opt.queue;

        this.zoom(opt.zoom);
    },

    graffitiId: function () {
        return this._graffitiId;
    },

    sessionId: function () {
        return this._sessionId;
    },

    canvasPosition: function () {
        return $(this._canvas.element()).position();
    },

    canvasElement: function () {
        if (this._canvas == null)
            return null;

        return this._canvas.element();
    },

    colorElement: function () {
        return document.getElementById(this._colorId);
    },

    positioning: function (zoom) {
        var canvas = this._canvas;
        var parent = $(canvas.element()).parent();
        var windowSize = {
            width: $(window).width() / zoom,
            height: $(window).height() / zoom
        };
        var canvasSize = {
            width: parseInt(this._size.width),
            height: parseInt(this._size.height)
        };
        // left
        if (windowSize.width - canvasSize.width >= 0) {
            canvasSize.left = ((windowSize.width - canvasSize.width) / 2);
        }
        // top
        if (windowSize.height - canvasSize.height >= 0) {
            canvasSize.top = ((windowSize.height - canvasSize.height) / 2);
        }

        // scaleとzoomだとダメっぽい
        $(canvas.element()).css(canvasSize);
        parent.css({ zoom: zoom });
        this._zoom = zoom;
    },

    // 以下をinterfaceとして見てね

    canvas: function () {
        return this._canvas;
    },

    queue: function (stroke, doRender) {
        if (stroke !== undefined) {
            this._queue.enqueue(stroke);
            if (doRender !== undefined && doRender)
                this.render();
        }

        return this._queue;
    },

    render: function () {
        // 自分で描画中ならレンダリングしない:inputのisDown
        if (!this.isDown())
            this.canvas().render(this._queue.strokes());
    },

    zoom: function (zoom) {
        if (zoom !== undefined) {
            this.positioning(zoom);
        }

        return this._zoom;
    },

    undo: function () {
        this._queue.enqueue({
            GraffitiId: this.graffitiId(),
            SessionId: this.sessionId(),
            Type: 'undo',
            Color: '',
            Weight: 0
        });
    },

    isMobileSafari: function () {
        return navigator.platform == "iPhone" ||
             navigator.platform == "iPad";
    }
}